package com.totoro.springboot.dynamic_load.util;

import com.sun.tools.attach.AgentInitializationException;
import com.sun.tools.attach.AgentLoadException;
import com.sun.tools.attach.AttachNotSupportedException;
import com.sun.tools.attach.VirtualMachine;
import com.totoro.agent.JavaDynAgent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import sun.jvmstat.monitor.*;
import sun.misc.Launcher;

import java.io.*;
import java.lang.instrument.ClassDefinition;
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
import java.lang.management.ManagementFactory;
import java.lang.reflect.Field;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * Created by Niki on 2018/4/10 20:07
 */
public class AgentUtil {
    private static final Logger LOGGER = LoggerFactory.getLogger(AgentUtil.class);
    private static String classesPath;
    private static String agentParth;
    private static VirtualMachine virtualMachine;
    private static String pid;

    static {
        classesPath = AgentUtil.class.getClassLoader().getResource("").getPath();
        if (classesPath.contains("%20")) {
            classesPath = classesPath.replace("%20", " ");
        }
        LOGGER.info("类文件路径：{}", classesPath);
        agentParth = getJarPath();

        //当前进程
        String name = ManagementFactory.getRuntimeMXBean().getName();
        pid = name.split("@")[0];
        LOGGER.info("当前进程pid:{}", pid);

        allProcessId();
    }

    private static boolean useFixedUrl = false;

    private static String getJarPath() {
        URL url = JavaDynAgent.class.getProtectionDomain().getCodeSource().getLocation();

        String filePath = null;
        String path = url.getPath();
        LOGGER.info("**********agent jar path is：{}", path);
        try {
            filePath = URLDecoder.decode(path, "utf-8");

        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        LOGGER.info("**********agent jar filePath is：{}", filePath);
        if (filePath.endsWith(".jar")) {
            File file = new File(filePath);

            return file.getAbsolutePath();
        } else {
            //无法获取到Agent的jar时通过写死来获得
            if (!useFixedUrl) {
                throw new RuntimeException("无法获取Agent jar包路径");
            }
            //该路径为服务器上agent的绝对路径
            filePath = "D:\\maven_repo\\com\\totoro\\agent\\1.0.0\\agent-1.0.0.jar";

            File file = new File(filePath);
            LOGGER.info("filePath 不符合规定！写死filePath is:{},返回其绝对路径为：{}", filePath, file.getAbsolutePath());
            return file.getAbsolutePath();
        }

//        return null;
    }

    public static void init() {
        try {
            virtualMachine = VirtualMachine.attach(pid);
            LOGGER.info("**** 代理路径为：{}", agentParth);
            virtualMachine.loadAgent(agentParth);
            virtualMachine.getAgentProperties();

            LOGGER.info("**** virtualMachine 类的加载器为：{}", virtualMachine.getClass().getClassLoader());
            LOGGER.info("**** JavaDynAgent的类加载器为：{}", JavaDynAgent.class.getClassLoader());
            LOGGER.info("**** jdk的类String的类加载器为：{}", String.class.getClassLoader());
        } catch (AttachNotSupportedException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (AgentLoadException e) {
            e.printStackTrace();
        } catch (AgentInitializationException e) {
            e.printStackTrace();
        }
        LOGGER.info("****虚拟机加载代理成功!");

//        Instrumentation instrumentation = JavaDynAgent.getInstrumentation();
        Instrumentation instrumentation = getInstrumentation();


        LOGGER.info("Launcher ：{}", Launcher.getLauncher().getClassLoader());
        ClassLoader classLoader = JavaDynAgent.class.getClassLoader();
        do {
            classLoader = classLoader.getParent();
            LOGGER.info("JavaDynAgent 的类加载器为：{}，类型为：{}", classLoader);
        } while (classLoader != null);

        if (instrumentation == null) {
            throw new RuntimeException("initInstrumentation must not be null");
        }
    }

    public static void javaAgent(String root, String[] classArr) {
        init();
        List<ClassDefinition> classDefinitions = new ArrayList<>();
        for (String className : classArr) {
            try {
                Class<?> c = Class.forName(className);
                String classPath = (!StringUtils.isEmpty(root) ? root : classesPath) + className.replace(".", "/") + ".class";
                LOGGER.info("class redefined:{}", classPath);

                byte[] bytesFromFile = getBytes(classPath);
                ClassDefinition classDefinition = new ClassDefinition(c, bytesFromFile);
                classDefinitions.add(classDefinition);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } finally {
                try {
                    destroy();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        try {
//            JavaDynAgent.getInstrumentation().redefineClasses(classDefinitions.toArray(new ClassDefinition[classDefinitions.size()]));
            getInstrumentation().redefineClasses(classDefinitions.toArray(new ClassDefinition[classDefinitions.size()]));
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (UnmodifiableClassException e) {
            e.printStackTrace();
        }
    }

    private static void destroy() throws IOException {
        if (virtualMachine != null) {
            virtualMachine.detach();
        }
    }

    public static byte[] getBytes(String filePath) {
        File javaFile = new File(filePath);
        InputStream inputStream = null;
        try {
            inputStream = new FileInputStream(javaFile);
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int len = 0;
            while ((len = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, len);
            }

            byte[] clazzInfo = outputStream.toByteArray();
            return clazzInfo;
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    private static void allProcessId() {
        try {
            MonitoredHost local = MonitoredHost.getMonitoredHost("localhost");
            Set<?> vmlist = new HashSet<>(local.activeVms());
            for (Object process : vmlist) {
                MonitoredVm vm = local.getMonitoredVm(new VmIdentifier("//" + process));
                // 获取类名
                String processname = MonitoredVmUtil.mainClass(vm, true);
                System.out.println("****" + process + " ------> " + processname);
            }
        } catch (MonitorException e) {
            e.printStackTrace();
        } catch (URISyntaxException e) {
            e.printStackTrace();
        }
    }

    public static String getClassPath(Class clazz) {
        if (StringUtils.isEmpty(classesPath)) {
            classesPath = clazz.getClassLoader().getResource("").getPath();
        }
        LOGGER.info("根路径为：{}", classesPath);
        String path = classesPath + clazz.getName().replace(".", "/") + ".class";
        LOGGER.info("类class文件路径：{}", path);
        return path;
    }

    /**
     * 获取代理中回调进去的Instrumentation
     * 因为Tomcat服务器中加载的类对象所有的加载器与java默认的加载器不一致，这里需要手工调试对应的类加载器
     *
     * @return
     */
    private static Instrumentation getInstrumentation() {
        try {
            /* Tomcat上下文使用的类加载器为：ParallelWebappClassLoader
                context: ROOT
                delegate: false
                ----------> Parent Classloader:
                java.net.URLClassLoader@78a2da20  不是java默认的类加载器：sun.misc.Launcher$AppClassLoader */
//            Class clazz = JavaDynAgent.class.getClassLoader().getParent().getParent().loadClass(JavaDynAgent.class.getName());
            Class clazz = Launcher.getLauncher().getClassLoader().loadClass(JavaDynAgent.class.getName());
            Field field = clazz.getDeclaredField("instrumentation");
            field.setAccessible(true);
            Instrumentation obj = (Instrumentation) field.get(clazz);
            return obj;
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return null;
    }
}
